import pandas as pd
import datetime
import json

from coin.base.query_util import (query_klines, query_pta)
from coin.strategy.dmm_strat.analysis.exchange.binance.binance_dmm_parser import \
    BinanceSpotParser, BinanceCoinMarginedFuturesParser


class BinanceDMMAnalysis(object):
  def __init__(self, dmm_type, score_dir, rqmt_path):
    self._dmm_type = dmm_type
    self._parser = None
    self._market_type = None
    if dmm_type == 'spot':
      self._parser = BinanceSpotParser(score_dir)
      self._market_type = 'spot'
    elif dmm_type == 'coin_margined':
      self._parser = BinanceCoinMarginedFuturesParser(score_dir)
      self._market_type = 'futures'
    else:
      raise ValueError('DMM type value error: {dmm_type}')
    self._map_symbol_weight = self._parser.parse_rqmt(rqmt_path)
    self._quoting_list = self._map_symbol_weight.keys()

  def _get_symbol_turnover(self, start_time, end_time):
    df = query_klines(start_time, end_time)
    df = df.loc[(df['market_type'] == self._market_type.title()) & (
        df['exchange'] == 'Binance') & df['symbol'].isin(self._quoting_list)]
    map_symbol_turnover = {}
    for _, row in df.iterrows():
      if row.symbol not in map_symbol_turnover:
        map_symbol_turnover[row.symbol] = float(
            json.loads(row.kline_dict)['klines'][0]['turnover_in_usd'])
      else:
        map_symbol_turnover[row.symbol] += float(json.loads(row.kline_dict)
                                                 ['klines'][0]['turnover_in_usd'])
    return map_symbol_turnover

  def _get_turnover_adjusted_weight(self, start_time, end_time):
    map_symbol_turnover = self._get_symbol_turnover(start_time, end_time)
    map_symbol_scores = {}
    for k, v in map_symbol_turnover.items():
      map_symbol_scores[k] = v * self._map_symbol_weight[k]
    scores = map_symbol_scores.values()
    map_symbol_adj_weight = dict(zip(map_symbol_scores.keys(), [s / sum(scores) for s in scores]))
    return map_symbol_adj_weight

  def _get_market_share(self, start_time, end_time):
    df_pta = query_pta(start_time, end_time, business_units=['Coin'])
    df_pta = df_pta.loc[(df_pta['market_type'] == self._market_type.title()) & (
        df_pta['exchange'] == 'Binance') & df_pta['symbol'].isin(self._quoting_list)]
    df_pq = df_pta.loc[:, ['symbol', 'fill_pq_maker', 'fill_pq_maker_inferred',
                           'fill_pq', 'fill_pq_in_usd']].copy().groupby(['symbol'], as_index=False).sum()
    pq = df_pq.apply(lambda x: (x['fill_pq_maker'] + x['fill_pq_maker_inferred'])
                     * x['fill_pq_in_usd'] / x['fill_pq'] if x['fill_pq'] != 0 else 0, axis=1)
    map_symbol_pq = dict(zip(df_pq['symbol'], pq))
    map_symbol_turnover = self._get_symbol_turnover(start_time, end_time)
    market_share = {}
    for k, v in map_symbol_turnover.items():
      if k in map_symbol_pq:
        market_share[k] = float(map_symbol_pq[k]) / v
      else:
        market_share[k] = 0
    return market_share

  def _get_qualified_ratios(self, start_time, end_time):
    cur_time = start_time
    parser = self._parser
    qualif_ratio_dict = {}
    duration = 0
    while cur_time <= end_time:
      df = parser.parse_daily_score(cur_time)[0].copy()
      ds_dict = dict(zip(df['symbol'], df['qualified_ratio']))
      for symbol, ratio in ds_dict.items():
        if symbol not in qualif_ratio_dict:
          qualif_ratio_dict[symbol] = ratio
        else:
          qualif_ratio_dict[symbol] += ratio
      cur_time += datetime.timedelta(days=1)
      duration += 1
    result_dict = {k: v / duration for k, v in qualif_ratio_dict.items()}
    return result_dict

  def _get_sorted_info(self, start_time, end_time):
    adj_weight = self._get_turnover_adjusted_weight(start_time, end_time)
    ms = self._get_market_share(start_time, end_time)
    qr = self._get_qualified_ratios(start_time, end_time)
    labels = adj_weight.keys()
    scores = adj_weight.values()
    scores_out = sorted(scores, reverse=True)
    labels_out = [x for _, x in sorted(zip(scores, labels), reverse=True)]
    market_share_out = [x for _, x in sorted(zip(scores, ms.values()), reverse=True)]
    df = pd.DataFrame({
                        'symbol': labels_out,
                        'turnover_adjusted_weight': scores_out,
                        'maker_market_share': market_share_out
                      })
    df2 = pd.DataFrame(qr.items(), columns=['symbol', 'qualified_ratio'])
    df_merged = pd.merge(df, df2, how='left', on='symbol')
    df_merged['score_weight'] = df_merged.loc[:, 'turnover_adjusted_weight'] * \
      df_merged.loc[:, 'maker_market_share'] * df_merged.loc[:, 'qualified_ratio']
    return df_merged

  def _get_total_info(self, df_sorted):
    quali_coverage = 1
    for _, row in df_sorted.iterrows():
      if pd.isnull(row['qualified_ratio']) or row['qualified_ratio'] == 0:
        quali_coverage -= row['turnover_adjusted_weight']
    adj_qualified_ratio = df_sorted.loc[:,
                                        'turnover_adjusted_weight'] * df_sorted.loc[:, 'qualified_ratio']
    total_info = {'turnover_adjusted_weight': df_sorted['turnover_adjusted_weight'].sum(),
                  'maker_market_share': (df_sorted.loc[:, 'turnover_adjusted_weight'] * df_sorted.loc[:, 'maker_market_share']).sum(),
                  'qualified_ratio': adj_qualified_ratio.sum() / quali_coverage,
                  'score_weight': df_sorted['score_weight'].sum(),
                  'quali_coverage': quali_coverage}
    return total_info

  def _get_potential_scores(self, df_sorted, threshold_ms=1, threshold_qualif=75):
    threshold_ms /= 100
    threshold_qualif /= 100
    df = df_sorted.loc[(df_sorted['maker_market_share'] < threshold_ms)]
    old_score_weight = df['score_weight']
    if df.empty:
      raise ValueError('market share threshold too low, no data return')
    else:
      df.loc[:, 'maker_market_share'] = threshold_ms - df.loc[:, 'maker_market_share']
      df.loc[:, 'qualified_ratio'] = df['qualified_ratio'].apply(
          lambda x: threshold_qualif if x < threshold_qualif else x)
      new_score_weight = df.loc[:, 'turnover_adjusted_weight'] * threshold_ms * (df.loc[:, 'qualified_ratio'])
      df.loc[:, 'extra_pct_in_potential'] = (new_score_weight - old_score_weight) / df_sorted['score_weight'].sum()
    return df

  def _get_trader_turnover(self, strat_time, end_time):
    df_pta = query_pta(strat_time, end_time, business_units=['Coin'])
    df_pta = df_pta.loc[(df_pta['market_type'] == self._market_type.title()) & (
        df_pta['exchange'] == 'Binance') & df_pta['symbol'].isin(self._quoting_list)]
    df = df_pta[['trader', 'symbol', 'fill_pq_maker', 'fill_pq_maker_inferred', 'fill_pq',
                 'fill_pq_in_usd']].copy().groupby(['trader', 'symbol'], as_index=False).sum()
    trader_list = df['trader'].unique()
    df_turnover = pd.DataFrame(0, index=trader_list, columns=self._quoting_list)
    for _, row in df.iterrows():
      if row.fill_pq != 0:
        df_turnover.loc[row.trader, row.symbol] = round(self._map_symbol_weight[row.symbol] * float(
            row.fill_pq_maker + row.fill_pq_maker_inferred) * float(row.fill_pq_in_usd) / float(row.fill_pq), 2)
    df_turnover.loc['Total'] = df_turnover.sum()
    df_turnover.loc[:, 'Total'] = df_turnover.sum(axis=1)
    return df_turnover

  def _get_daily_trader_adj_turnover(self, trading_date):
    df_pta = query_pta(trading_date, trading_date, business_units=['Coin'])
    df_pta = df_pta.loc[(df_pta['market_type'] == self._market_type.title()) & (
        df_pta['exchange'] == 'Binance') & df_pta['symbol'].isin(self._quoting_list) & df_pta['fill_pq'] != 0]
    df = df_pta[['trader', 'symbol', 'fill_pq_maker', 'fill_pq_maker_inferred', 'fill_pq',
                 'fill_pq_in_usd']].copy().groupby(['trader', 'symbol'], as_index=False).sum()
    df.loc[:, 'adjusted_turnover'] = [round(self._map_symbol_weight[row.symbol] *
                                            float(row.fill_pq_maker + row.fill_pq_maker_inferred) * float(row.fill_pq_in_usd) / float(row.fill_pq), 2) for _, row in df.iterrows()]
    df_adj_turnover = df[['trader', 'adjusted_turnover']
                         ].copy().groupby(['trader'], as_index=False).sum()
    df_adj_turnover = df_adj_turnover.set_index('trader')
    df_adj_turnover.index.name = None
    return df_adj_turnover

  def _get_daily_adj_qualified_ratio(self, trading_date):
    df = self._get_sorted_info(trading_date, trading_date)
    total = self._get_total_info(df)
    return total['qualified_ratio']

  def _get_daily_weekly_ranking_info(self, trading_date):
    parser = self._parser
    infos = parser.parse_daily_score(trading_date)[1]
    d_ranking = infos['daily_ranking'].replace('%', '')
    w_ranking = infos['cumulative_weekly_ranking'].replace('%', '')
    if self._dmm_type == 'coin_margined':
      map_tier = {'0~25': 0, '25~50': 1, '50~75': 2, '75~100': 3, 'N/A': 4}
      d_ranking = map_tier[d_ranking]
      w_ranking = map_tier[w_ranking]
    return (float(d_ranking), float(w_ranking))

  def _get_daily_weekly_vol_pct(self, trading_date):
    parser = self._parser
    infos = parser.parse_daily_score(trading_date)[1]
    d_vol_pct = infos['daily_total_maker_volume_pct'].replace('%', '')
    w_vol_pct = infos['cumultative_weekly_total_maker_volume_pct'].replace('%', '')
    return (float(d_vol_pct), float(w_vol_pct))

  def get_results(self, start_time, end_time):
    df = self._get_sorted_info(start_time, end_time)
    potential_scores = self._get_potential_scores(df)
    trader_turnover = self._get_trader_turnover(start_time, end_time)
    total = self._get_total_info(df)
    daily_trader_adj_turnover = []
    daily_daily_adj_qualified_ratio = []
    weekly_ranking = []
    daily_ranking = []
    weekly_vol_pct = []
    daily_vol_pct = []
    cur_date = start_time
    while cur_date <= end_time:
      daily, weekly = self._get_daily_weekly_ranking_info(cur_date)
      daily_ranking.append(daily)
      weekly_ranking.append(weekly)
      if self._market_type == 'futures':
        d_vol_pct, w_vol_pct = self._get_daily_weekly_vol_pct(cur_date)
        daily_vol_pct.append(d_vol_pct)
        weekly_vol_pct.append(w_vol_pct)
      daily_trader_adj_turnover.append(self._get_daily_trader_adj_turnover(cur_date))
      daily_daily_adj_qualified_ratio.append(self._get_daily_adj_qualified_ratio(cur_date))
      cur_date += datetime.timedelta(days=1)
    daily_summary = {'daily_ranking': daily_ranking,
                     'weekly_ranking': weekly_ranking,
                     'daily_trader_adj_turnover': daily_trader_adj_turnover,
                     'daily_daily_adj_qualified_ratio': daily_daily_adj_qualified_ratio}
    result_dict = {'symbol': df['symbol'],
                   'turnover_adjusted_weight': df['turnover_adjusted_weight'],
                   'maker_market_share': df['maker_market_share'],
                   'qualified_ratio': df['qualified_ratio'],
                   'score_weight': df['score_weight'],
                   'total': total,
                   'potential_scores': potential_scores,
                   'trader_turnover': trader_turnover,
                   'daily_summary': daily_summary,
                   'daily_volume_pct': daily_vol_pct,
                   'weekly_volume_pct': weekly_vol_pct}
    return result_dict
